//=============================================================================
//
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//
//  Copyright 2012 Sandia Corporation.
//  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
//  the U.S. Government retains certain rights in this software.
//
//=============================================================================
#ifndef __dax_testing_Testing_h
#define __dax_testing_Testing_h

#include <dax/CellTag.h>
#include <dax/CellTraits.h>
#include <dax/Types.h>
#include <dax/TypeTraits.h>
#include <dax/VectorTraits.h>

#include <iostream>
#include <sstream>
#include <string>

#include <math.h>

// Try to enforce using the correct testing version. (Those that include the
// control environment have more possible exceptions.) This is not guaranteed
// to work. To make it more likely, place the Testing.h include last.
#ifdef __dax_cont_Error_h
#ifndef __dax_cont_testing_Testing_h
#error Use dax::cont::testing::Testing instead of dax::testing::Testing.
#else
#define DAX_TESTING_IN_CONT
#endif
#endif

/// \def DAX_TEST_ASSERT(condition, message)
///
/// Asserts a condition for a test to pass. A passing condition is when \a
/// condition resolves to true. If \a condition is false, then the test is
/// aborted and failure is returned.

#define DAX_TEST_ASSERT(condition, message) \
  ::dax::testing::Testing::Assert( \
      condition, __FILE__, __LINE__, message, #condition)

/// \def DAX_TEST_FAIL(message)
///
/// Causes a test to fail with the given \a message.

#define DAX_TEST_FAIL(message) \
  throw ::dax::testing::Testing::TestFailure(__FILE__, __LINE__, message)

namespace dax {
namespace testing {

struct Testing
{
public:
  class TestFailure
  {
  public:
    DAX_CONT_EXPORT TestFailure(const std::string &file,
                                dax::Id line,
                                const std::string &message)
      : File(file), Line(line), Message(message) { }

    DAX_CONT_EXPORT TestFailure(const std::string &file,
                                dax::Id line,
                                const std::string &message,
                                const std::string &condition)
      : File(file), Line(line)
    {
      this->Message.append(message);
      this->Message.append(" (");
      this->Message.append(condition);
      this->Message.append(")");
    }

    DAX_CONT_EXPORT const std::string &GetFile() const { return this->File; }
    DAX_CONT_EXPORT dax::Id GetLine() const { return this->Line; }
    DAX_CONT_EXPORT const std::string &GetMessage() const {
      return this->Message;
    }
  private:
    std::string File;
    dax::Id Line;
    std::string Message;
  };

  static DAX_CONT_EXPORT void Assert(bool condition,
                                     const std::string &file,
                                     dax::Id line,
                                     const std::string &message,
                                     const std::string &conditionString)
  {
    if (condition)
      {
      // Do nothing.
      }
    else
      {
      throw TestFailure(file, line, message, conditionString);
      }
  }

#ifndef DAX_TESTING_IN_CONT
  /// Calls the test function \a function with no arguments. Catches any errors
  /// generated by DAX_TEST_ASSERT or DAX_TEST_FAIL, reports the error, and
  /// returns "1" (a failure status for a program's main). Returns "0" (a
  /// success status for a program's main).
  ///
  /// The intention is to implement a test's main function with this. For
  /// example, the implementation of UnitTestFoo might look something like
  /// this.
  ///
  /// \code
  /// #include <dax/testing/Testing.h>
  ///
  /// namespace {
  ///
  /// void TestFoo()
  /// {
  ///    // Do actual test, which checks in DAX_TEST_ASSERT or DAX_TEST_FAIL.
  /// }
  ///
  /// } // anonymous namespace
  ///
  /// int UnitTestFoo(int, char *[])
  /// {
  ///   return dax::testing::Testing::Run(TestFoo);
  /// }
  /// \endcode
  ///
  template<class Func>
  static DAX_CONT_EXPORT int Run(Func function)
  {
    try
      {
      function();
      }
    catch (TestFailure error)
      {
      std::cout << "***** Test failed @ "
                << error.GetFile() << ":" << error.GetLine() << std::endl
                << error.GetMessage() << std::endl;
      return 1;
      }
    catch (...)
      {
      std::cout << "***** Unidentified exception thrown." << std::endl;
      return 1;
      }
    return 0;
  }
#endif

  /// Check functors to be used with the TryAllTypes method.
  ///
  struct TypeCheckAlwaysTrue {
    template <typename T, class Functor>
    void operator()(T t, Functor function) const { function(t); }
  };
  struct TypeCheckInteger {
    template <typename T, class Functor>
    void operator()(T t, Functor function) const {
      this->DoInteger(typename dax::TypeTraits<T>::NumericTag(), t, function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoInteger(Tag, T, const Functor&) const {  }
    template <typename T, class Functor>
    void DoInteger(dax::TypeTraitsIntegerTag, T t, Functor function) const {
      function(t);
    }
  };
  struct TypeCheckReal {
    template <typename T, class Functor>
    void operator()(T t, Functor function) const {
      this->DoReal(typename dax::TypeTraits<T>::NumericTag(), t, function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoReal(Tag, T, const Functor&) const {  }
    template <typename T, class Functor>
    void DoReal(dax::TypeTraitsRealTag, T t, Functor function) const {
      function(t);
    }
  };
  struct TypeCheckScalar {
    template <typename T, class Functor>
    void operator()(T t, Functor func) const {
      this->DoScalar(typename dax::TypeTraits<T>::DimensionalityTag(), t, func);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoScalar(Tag, const T &, const Functor &) const {  }
    template <typename T, class Functor>
    void DoScalar(dax::TypeTraitsScalarTag, T t, Functor function) const {
      function(t);
    }
  };
  struct TypeCheckVector {
    template <typename T, class Functor>
    void operator()(T t, Functor func) const {
      this->DoVector(typename dax::TypeTraits<T>::DimensionalityTag(), t, func);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoVector(Tag, const T &, const Functor &) const {  }
    template <typename T, class Functor>
    void DoVector(dax::TypeTraitsVectorTag, T t, Functor function) const {
      function(t);
    }
  };

  template<class FunctionType>
  struct InternalPrintOnInvoke {
    InternalPrintOnInvoke(FunctionType function, std::string toprint)
      : Function(function), ToPrint(toprint) { }
    template <typename T> void operator()(T t) {
      std::cout << this->ToPrint << std::endl;
      this->Function(t);
    }
  private:
    FunctionType Function;
    std::string ToPrint;
  };

  /// Runs templated \p function on all the basic types defined in Dax. This is
  /// helpful to test templated functions that should work on all types. If the
  /// function is supposed to work on some subset of types, then \p check can
  /// be set to restrict the types used. This Testing class contains several
  /// helpful check functors.
  ///
  template<class FunctionType, class CheckType>
  static void TryAllTypes(FunctionType function, CheckType check)
  {
    dax::Id id = 0;
    check(id, InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::Id ***************"));

    dax::Id3 id3 = dax::make_Id3(0, 0, 0);
    check(id3, InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::Id3 **************"));

    dax::Scalar scalar = 0.0;
    check(scalar, InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::Scalar ***********"));

    dax::Vector2 vector2 = dax::make_Vector2(0.0, 0.0);
    check(vector2, InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::Vector2 **********"));

    dax::Vector3 vector3 = dax::make_Vector3(0.0, 0.0, 0.0);
    check(vector3, InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::Vector3 **********"));

    dax::Vector4 vector4 = dax::make_Vector4(0.0, 0.0, 0.0, 0.0);
    check(vector4, InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::Vector4 **********"));
  }
  template<class FunctionType>
  static void TryAllTypes(FunctionType function)
  {
    TryAllTypes(function, TypeCheckAlwaysTrue());
  }

  /// Check functors to be used with the TryAllTypes method.
  ///
  struct CellCheckAlwaysTrue {
    template <class Tag, class Functor>
    void operator()(Tag t, Functor function) const { function(t); }
  };
  struct CellCheckUniform {
    template <class Tag, class Functor>
    void operator()(Tag t, Functor function) const {
      this->DoUniform(typename dax::CellTraits<Tag>::GridTag(), t, function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoUniform(Tag, T, const Functor&) const {  }
    template <class Tag, class Functor>
    void DoUniform(dax::GridTagUniform, Tag t, Functor function) const {
      function(t);
    }
  };
  struct CellCheckUnstructured {
    template <class Tag, class Functor>
    void operator()(Tag t, Functor function) const {
      this->DoUnstructured(typename dax::CellTraits<Tag>::GridTag(), t, function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoUnstructured(Tag, T, const Functor&) const {  }
    template <class Tag, class Functor>
    void DoUnstructured(dax::GridTagUnstructured, Tag t, Functor function) const {
      function(t);
    }
  };
  struct CellCheckHexahedron {
    template <class Tag, class Functor>
    void operator()(Tag t, Functor function) const {
      this->DoHexahedron(
            typename dax::CellTraits<Tag>::CanonicalCellTag(),
            t,
            function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoHexahedron(Tag, T, const Functor&) const {  }
    template <class Tag, class Functor>
    void DoHexahedron(dax::CellTagHexahedron, Tag t, Functor function) const {
      function(t);
    }
  };
  struct CellCheckTetrahedron {
    template <class Tag, class Functor>
    void operator()(Tag t, Functor function) const {
      this->DoTetrahedron(
            typename dax::CellTraits<Tag>::CanonicalCellTag(),
            t,
            function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoTetrahedron(Tag, T, const Functor&) const {  }
    template <class Tag, class Functor>
    void DoTetrahedron(dax::CellTagTetrahedron, Tag t, Functor function) const {
      function(t);
    }
  };
  template<int Dims>
  struct CellCheckTopologicalDimensions {
    template <class Tag, class Functor>
    void operator()(Tag t, Functor function) const {
      this->DoTopologicalDimensions(
            typename dax::CellTraits<Tag>::TopologicalDimensionsTag(),
            t,
            function);
    }
  private:
    template <class Tag, typename T, class Functor>
    void DoTopologicalDimensions(Tag, T, const Functor&) const {  }
    template <class Tag, class Functor>
    void DoTopologicalDimensions(dax::CellTopologicalDimensionsTag<Dims>,
                                 Tag t,
                                 Functor function) const {
      function(t);
    }
  };

  /// Runs templated \p function on all the cell tags defined in Dax. This is
  /// helpful to test templated functions that should work on all cell types.
  /// If the function is supposed to work on some subset of cell types, then \p
  /// check can be set to restrict the types used. This Testing class contains
  /// several helpful check functors.
  ///
  template<class FunctionType, class CheckType>
  static void TryAllCells(FunctionType function, CheckType check)
  {
    check(dax::CellTagVoxel(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagVoxel ******************"));

    check(dax::CellTagVertex(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagVertex *****************"));

    check(dax::CellTagLine(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagLine *******************"));

    check(dax::CellTagTriangle(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagTriangle ***************"));

    check(dax::CellTagQuadrilateral(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagQuadrilateral **********"));

    check(dax::CellTagHexahedron(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagHexahedron *************"));

    check(dax::CellTagTetrahedron(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagTetrahedron ************"));

    check(dax::CellTagWedge(), InternalPrintOnInvoke<FunctionType>(
            function, "*** dax::CellTagWedge ******************"));
  }
  template<class FunctionType>
  static void TryAllCells(FunctionType function)
  {
    TryAllCells(function, CellCheckAlwaysTrue());
  }
};

}
} // namespace dax::internal

/// Helper function to test two quanitites for equality accounting for slight
/// variance due to floating point numerical inaccuracies.
///
template<typename VectorType>
DAX_EXEC_CONT_EXPORT bool test_equal(VectorType vector1,
                                     VectorType vector2,
                                     dax::Scalar tolerance = 0.0001)
{
  typedef typename dax::VectorTraits<VectorType> Traits;
  for (int component = 0; component < Traits::NUM_COMPONENTS; component++)
    {
    dax::Scalar value1 = Traits::GetComponent(vector1, component);
    dax::Scalar value2 = Traits::GetComponent(vector2, component);
    if ((fabs(value1) < 2*tolerance) && (fabs(value2) < 2*tolerance))
      {
      continue;
      }
    dax::Scalar ratio = value1/value2;
    if ((ratio > dax::Scalar(1.0) - tolerance)
        && (ratio < dax::Scalar(1.0) + tolerance))
      {
      // This component is OK. The condition is checked in this way to
      // correctly handle non-finites that fail all comparisons. Thus, if a
      // non-finite is encountered, this condition will fail and false will be
      // returned.
      }
    else
      {
      return false;
      }
    }
  return true;
}

/// Helper function for printing out vectors during testing.
///
template<typename T, int Size>
DAX_EXEC_CONT_EXPORT
std::ostream &operator<<(std::ostream &stream, const dax::Tuple<T,Size> &tuple)
{
  stream << "[";
  for (int component = 0; component < Size-1; component++)
    {
    stream << tuple[component] << ",";
    }
  return stream << tuple[Size-1] << "]";
}

#endif //__dax_testing_Testing_h
